#include "vanila/baseobject.h"
#include "vanila/virtualmachine.h"
#include "utils/format.h"

namespace vanila
{
static VirtualMachine* vm = utils::Singleton<VirtualMachine>::instance();

//! \brief Construct a new Object Instance:: Object Instance object
ObjectInstance::ObjectInstance(ObjectClass* klass):
    Object{ObjectType::INSTANCE}, 
    _klass{klass}, 
    _value{nullptr}
{
    if (!klass->isNative())
        this->_fields = new std::map<ObjectString*, Value>{};
}

ObjectInstance::~ObjectInstance() noexcept
{
    if (!this->isNative())
        delete this->_fields;
}

//! \brief print object instance
//! \param[in] callStr wheather call '__str__' method to print
void ObjectInstance::print(bool callStr) const
{
    if (this->isNative())
    {
        this->_object->print();
        return;
    }

    if (callStr)
    {
        // search '__str__'
        Value strMethod;
        if (this->_klass->findStrMethod(strMethod))
        {
            Value res = vm->callIntanceMethod(this, strMethod);
            if (!res.isString())
                vm->runtimeError("'__str__' method should return an string");
            res.asString()->print();
            return;
        }
    }
        
    // the instance is not a native, and do not has 'str' method
    std::cout << utils::format("%s instance", this->_klass->name()->cstr());
}

//! \brief get object instance's hash value
int64_t ObjectInstance::hash() const
{
    // try find '__hash__' method
    Value hashMethod;
    if (this->_klass->findHashMethod(hashMethod))
    {
        Value res = vm->callIntanceMethod(this, hashMethod);
        if (!res.isInteger())
            vm->runtimeError("'__hash__' method should return an integer");
        return res.integer();
    }

    return Object::hash();
}

//! \brief wheather this == that
bool ObjectInstance::equal(const Object* that) const
{
    // a native instance!
    if (this->isNative())
    {
        const ObjectInstance* instance = that->asInstance();
        if (instance->isNative() && this->_object->type() == instance->_object->type())
            return this->_object->equal(instance->_object);

        vm->runtimeError(utils::format("'==' not supported between '%s' and '%s'", 
                         Object::label(this->_object->type()), Object::label(instance->_object->type())));
        return false;
    }

    // try call '__equal__'
    Value method;
    if (this->_klass->findEqualMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        if (!res.isBoolean())
            vm->runtimeError("'__equal__' method should return an boolean.");
        return res.boolean();
    }

    return Object::equal(that);
}

//! \brief wheather this < that
bool ObjectInstance::less(const Object* that) const
{
    // try call '__less__'
    Value method;
    if (this->_klass->findLessMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        if (!res.isBoolean())
            vm->runtimeError("'__less__' method should return an integer.");
        return res.boolean();
    }

    vm->runtimeError(utils::format("the instance of '%s' do not support '<' operator.", this->_klass->name()->cstr()));
    return false;
}

//! \brief wheather this > that
bool ObjectInstance::greater(const Object* that) const
{
    // try call '__greater__'
    Value method;
    if (this->_klass->findGreaterMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        if (!res.isBoolean())
            vm->runtimeError("'__greater__' method should return an integer");
        return res.boolean();
    }

    vm->runtimeError(utils::format("the instance of '%s' do not support '>' operator.", this->_klass->name()->cstr()));
    return false;
}

//! \brief add two object
Value ObjectInstance::add(const Object* that) const
{
    if (this->isNative())
    {
        const ObjectInstance* instance = that->asInstance();
        if (instance->isNative() && this->_object->type() == instance->_object->type())
            return this->_object->add(instance->_object);

        vm->runtimeError(utils::format("'+' not supported between '%s' and '%s'", 
                         Object::label(this->_object->type()), Object::label(instance->_object->type())));
        return Value{};
    }

    // try call '__add__'
    Value method;
    if (this->_klass->findAddMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        return res;
    }

    return Object::add(that);
}

//! \brief sub two object
Value ObjectInstance::sub(const Object* that) const
{
    // try call '__sub__'
    Value method;
    if (this->_klass->findSubMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        return res;
    }

    return Object::sub(that);
}

//! \brief mul two object
Value ObjectInstance::mul(const Object* that) const
{
    // try call '__mul__'
    Value method;
    if (this->_klass->findMulMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        return res;
    }

    return Object::mul(that);}

//! \brief div two object
Value ObjectInstance::div(const Object* that) const
{
    // try call '__div__'
    Value method;
    if (this->_klass->findDivMethod(method))
    {
        Value res = vm->callIntanceMethod(this, method, {Value{const_cast<Object*>(that)}});
        return res;
    }

    return Object::div(that);
}

//! \brief find the specify filed in instance
//! \param[in] name filed name object
//! \param[out] field field value reference
//! \return if no found, return false
bool ObjectInstance::findField(ObjectString* name, Value& field)
{
    auto iter = this->_fields->find(name);
    if (iter != this->_fields->end())
    {
        field = iter->second;
        return true;
    }
    return false;
}

}